package com.dana1.nos.storage;

import java.io.Serializable;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.TreeMap;
import java.util.UUID;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.NotNullPredicate;
import org.apache.commons.collections.iterators.FilterIterator;
import org.apache.commons.collections.iterators.TransformIterator;
import org.apache.commons.lang3.SerializationUtils;

import com.dana1.nos.Storage;
import com.dana1.nos.generator.TimeBasedUUIDGenerator;
import com.dana1.nos.util.MetaSupport;
import com.dana1.nos.util.MetaSupport.MetaKey;

public class MemoryStorage implements Storage {
	private TreeMap<String,Storable> storage = new TreeMap<>();
	
	private MetaKey<String> IDENTIFIER = new MetaKey<>("identifier");
	private MetaKey<UUID> VERSION = new MetaKey<>("version");
	
	private class Storable {
		Serializable data;
		UUID version;
		public Storable(Serializable data, UUID version) {
			super();
			this.data = data;
			this.version = version;
		}
	}
	
	public <T extends Serializable> String identifierFor(T obj) {
		return MetaSupport.meta(obj,IDENTIFIER);
	};
	
	@Override
	public <T extends Serializable> T get(Class<T> c, String identifier) {
		Storable s;
		synchronized (storage) {
			s = storage.get(identifier);
		}
		if (s==null)
			return null;
		@SuppressWarnings("unchecked")
		T obj = (T)SerializationUtils.clone(s.data);
		MetaSupport.meta(obj, IDENTIFIER, identifier);
		MetaSupport.meta(obj,  VERSION , s.version);
		return obj;
	}

	@SuppressWarnings("unchecked")
	@Override
	public <T extends Serializable> Iterator<T> getByPrefix(final Class<T> c,	final String identifier) {
		// this method is more strongly consistent than partial iteration from a storage tail map, and more importantly, will not cause a 
		// ConcurrentModificationException when storage is modified by some other thread.
		
		// obtain a set of keys:
		LinkedList<String> keys = new LinkedList<>();
		synchronized(storage) { 
			keys = new LinkedList<>(storage.subMap(identifier,toKey(identifier)).keySet()); 
		}
		// return an iteration of the objects for those keys, screening out any nulls  (which are possible if a removal happens while the iteration is in progress):
		return new FilterIterator(new TransformIterator(keys.iterator(), new Transformer() {
			@Override
			public Object transform(Object arg0) {
				return get(c,(String)arg0);
			}
		}), NotNullPredicate.INSTANCE);
	}

	@Override
	public <T extends Serializable> void put(String identifier, T obj) throws ConcurrentModificationException {
		String ident = MetaSupport.meta(obj, IDENTIFIER);
		if ((null!=ident)&&(identifier!=null)&&(!ident.equals(identifier)))
			throw new IllegalArgumentException("Object is already associated with a different identifier.");
		if (null==ident)
			ident=identifier;
		if (null==ident)
			throw new IllegalArgumentException("Object must have an identifier");
		
		UUID oldVersion = MetaSupport.meta(obj, VERSION);
		UUID newVersion = TimeBasedUUIDGenerator.timeBasedUUID();
		synchronized (storage) {
			Storable s = storage.get(ident);
			if ((s==null)&&(oldVersion!=null)) // object has a version, but there is no item in storage
				throw new ConcurrentModificationException("Unable to update, item removed");
			else if ((s!=null)&&(oldVersion==null)) // item found, but object doesnt have a version (insert over existing)
				throw new ConcurrentModificationException("Unable to insert, item exists");
			else if ((s!=null)&&(oldVersion!=null)&&(!s.version.equals(oldVersion))) // item found, objects version doesnt match (optimistic fail)
				throw new ConcurrentModificationException("Unable to update, item was modified by someone else");
			storage.put(ident, new Storable(SerializationUtils.clone(obj),newVersion));
		}
		MetaSupport.meta(obj, IDENTIFIER, ident);
		MetaSupport.meta(obj, VERSION, newVersion);
	}

	@Override
	public <T extends Serializable> void remove(T obj) throws ConcurrentModificationException {
		String ident = MetaSupport.meta(obj, IDENTIFIER);
		if (null==ident)
			throw new IllegalArgumentException("Object identifier unknown");
		UUID oldVersion = MetaSupport.meta(obj, VERSION);
		if (oldVersion==null) // object doesn't have meta on it
			throw new IllegalArgumentException("Object version unknown");
		synchronized (storage) {
			Storable s = storage.get(ident);
			if (s==null) // nothing to remove
				throw new ConcurrentModificationException("Nothing to remove");
			else if (!s.version.equals(oldVersion)) // version mismatch, optimistic fail
				throw new ConcurrentModificationException("Item is not current");
			storage.remove(ident);
		}
		MetaSupport.meta(obj, IDENTIFIER, null);
		MetaSupport.meta(obj, VERSION, null);
	}

	
	public <T extends Serializable> int countByPrefix(Class<T> c,String identifier) {
		synchronized(storage) { 
			return storage.subMap(identifier, toKey(identifier)).size();
		}
	}

		
	private static String toKey(String fromKey)
	{
		if (fromKey.length()==0)
			return "";
		return fromKey.substring(0, fromKey.length()-1)+(char)(fromKey.charAt(fromKey.length()-1)+1);
	}

}
